/*
 * ******************************************************************************
 *   Copyright (c) 2013-2014 Gabriele Mariotti.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *  *****************************************************************************
 */

package it.gmariotti.cardslib.library.view;

import it.gmariotti.cardslib.library.R;
import it.gmariotti.cardslib.library.internal.Card;
import it.gmariotti.cardslib.library.internal.CardArrayAdapter;
import it.gmariotti.cardslib.library.internal.CardCursorAdapter;
import it.gmariotti.cardslib.library.view.base.CardViewWrapper;
import it.gmariotti.cardslib.library.view.listener.SwipeOnScrollListener;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListAdapter;

/**
 * Card List View. It uses an {@link CardArrayAdapter} to populate items. </p>
 * Usage:
 * 
 * <pre>
 * <code>
 *    <it.gmariotti.cardslib.library.view.CardListView
 *      android:layout_width="match_parent"
 *      android:layout_height="match_parent"
 *      android:id="@+id/listId"
 *      card:list_card_layout_resourceID="@layout/list_card_thumbnail_layout" /> *
 * </code>
 * </pre>
 * 
 * It provides a default layout id for each row @layout/list_card_layout Use can
 * easily customize it using card:list_card_layout_resourceID attr in your xml
 * layout. </p> Use this code to populate the list view
 * 
 * <pre>
 * <code>
 * CardListView listView = (CardListView) getActivity().findViewById(R.id.listId);
 * listView.setAdapter(mCardArrayAdapter);
 * </code>
 * </pre>
 * 
 * </p> Currently you have to use the same inner layout for each card in
 * listView. </p>
 * 
 * @author Gabriele Mariotti (gabri.mariotti@gmail.com)
 */
public class CardListView extends PullToRefreshListView implements
		CardViewWrapper.OnExpandListAnimatorListener {

	protected static String TAG = "CardListView";

	/**
	 * Card Array Adapter
	 */
	protected CardArrayAdapter mAdapter;

	/**
	 * Card Cursor Adapter
	 */
	protected CardCursorAdapter mCursorAdapter;

	/**
	 * Custom ScrollListener to be used with a CardListView and cards with swipe
	 * action
	 */
	protected SwipeOnScrollListener mOnScrollListener;

	// --------------------------------------------------------------------------
	// Custom Attrs
	// --------------------------------------------------------------------------

	/**
	 * Default layout to apply to card
	 */
	protected int list_card_layout_resourceID = R.layout.list_card_layout;

	// --------------------------------------------------------------------------
	// Constructors
	// --------------------------------------------------------------------------

	public CardListView(Context context) {
		super(context);
		init(null, 0);
	}

	public CardListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(attrs, 0);
	}

	public CardListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(attrs, defStyle);
	}

	// --------------------------------------------------------------------------
	// Init
	// --------------------------------------------------------------------------

	/**
	 * Initialize
	 *
	 * @param attrs
	 * @param defStyle
	 */
	protected void init(AttributeSet attrs, int defStyle) {

		// Init attrs
		initAttrs(attrs, defStyle);

		// Set divider to 0dp
		setDividerHeight(0);

	}

	/**
	 * Init custom attrs.
	 *
	 * @param attrs
	 * @param defStyle
	 */
	protected void initAttrs(AttributeSet attrs, int defStyle) {

		list_card_layout_resourceID = R.layout.list_card_layout;

		TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs,
				R.styleable.card_options, defStyle, defStyle);

		try {
			list_card_layout_resourceID = a.getResourceId(
					R.styleable.card_options_list_card_layout_resourceID,
					this.list_card_layout_resourceID);
		} finally {
			a.recycle();
		}
	}

	// --------------------------------------------------------------------------
	// Adapter
	// --------------------------------------------------------------------------

	/**
	 * Set the adapter. You can provide a {@link CardArrayAdapter}, or a
	 * {@link it.gmariotti.cardslib.library.internal.CardCursorAdapter} or a
	 * generic adapter. Pay attention: your generic adapter has to call
	 * {@link CardArrayAdapter#getView} method
	 *
	 * @param adapter
	 */
	@Override
	public void setAdapter(ListAdapter adapter) {
		if (adapter instanceof CardArrayAdapter) {
			setAdapter((CardArrayAdapter) adapter);
		} else if (adapter instanceof CardCursorAdapter) {
			setAdapter((CardCursorAdapter) adapter);
		} else {
			Log.w(TAG,
					"You are using a generic adapter. Pay attention: your adapter has to call cardArrayAdapter#getView method");
			super.setAdapter(adapter);
		}
	}

	/**
	 * Set {@link CardArrayAdapter} and layout used by items in ListView
	 *
	 * @param adapter
	 *            {@link CardArrayAdapter}
	 */
	public void setAdapter(CardArrayAdapter adapter) {
		super.setAdapter(adapter);

		// Set Layout used by items
		adapter.setRowLayoutId(list_card_layout_resourceID);

		adapter.setCardListView(this);
		mAdapter = adapter;
	}

	/**
	 * Set {@link CardCursorAdapter} and layout used by items in ListView
	 *
	 * @param adapter
	 *            {@link CardCursorAdapter}
	 */
	public void setAdapter(CardCursorAdapter adapter) {
		super.setAdapter(adapter);

		// Set Layout used by items
		adapter.setRowLayoutId(list_card_layout_resourceID);

		adapter.setCardListView(this);
		mCursorAdapter = adapter;
	}

	/**
	 * You can use this method, if you are using external adapters. Pay
	 * attention. The generic adapter#getView() method has to call the
	 * cardArrayAdapter#getView() method to work.
	 *
	 * @param adapter
	 *            {@link ListAdapter} generic adapter
	 * @param cardArrayAdapter
	 *            {@link CardArrayAdapter} cardArrayAdapter
	 */
	public void setExternalAdapter(ListAdapter adapter, CardArrayAdapter cardArrayAdapter) {

		setAdapter(adapter);

		mAdapter = cardArrayAdapter;
		mAdapter.setCardListView(this);
		mAdapter.setRowLayoutId(list_card_layout_resourceID);
	}

	/**
	 * You can use this method, if you are using external adapters. Pay
	 * attention. The generic adapter#getView() method has to call the
	 * cardCursorAdapter#getView() method to work.
	 *
	 * @param adapter
	 *            {@link ListAdapter} generic adapter
	 * @param cardCursorAdapter
	 *            {@link CardCursorAdapter} cardArrayAdapter
	 */
	public void setExternalAdapter(ListAdapter adapter, CardCursorAdapter cardCursorAdapter) {

		setAdapter(adapter);

		mCursorAdapter = cardCursorAdapter;
		mCursorAdapter.setCardListView(this);
		mCursorAdapter.setRowLayoutId(list_card_layout_resourceID);
	}

	/**
	 * Returns local scroll event listener
	 */
	public OnScrollListener getOnScrollListener() {
		return this.mOnScrollListener;
	}

	/**
	 * Overrides the set on scroll listener method and registers local reference
	 */
	@Override
	public void setOnScrollListener(OnScrollListener mOnScrollListener) {
		super.setOnScrollListener(mOnScrollListener);
		if (mOnScrollListener instanceof SwipeOnScrollListener)
			this.mOnScrollListener = (SwipeOnScrollListener) mOnScrollListener;
	}

	// --------------------------------------------------------------------------
	// Expand and Collapse animator
	// --------------------------------------------------------------------------

	@Override
	public void onExpandStart(CardViewWrapper viewCard, View expandingLayout) {

		boolean expandable = true;
		if (mCursorAdapter != null) {
			expandable = mCursorAdapter.onExpandStart(viewCard);
		}

		if (expandable)
			ExpandCollapseHelper.animateExpanding(expandingLayout, viewCard, this);

		if (mCursorAdapter != null) {
			mCursorAdapter.onExpandEnd(viewCard);
		}

	}

	@Override
	public void onCollapseStart(CardViewWrapper viewCard, View expandingLayout) {
		boolean collapsible = true;
		if (mCursorAdapter != null) {
			collapsible = mCursorAdapter.onCollapseStart(viewCard);
		}

		if (collapsible)
			ExpandCollapseHelper.animateCollapsing(expandingLayout, viewCard, this);

		if (mCursorAdapter != null) {
			mCursorAdapter.onCollapseEnd(viewCard);
		}
	}

	/**
	 * Helper to animate collapse and expand animation
	 */
	private static class ExpandCollapseHelper {

		/**
		 * This method expandes the view that was clicked.
		 *
		 * @param expandingLayout
		 *            layout to expand
		 * @param cardView
		 *            cardView
		 * @param listView
		 *            listView
		 */
		public static void animateCollapsing(final View expandingLayout,
				final CardViewWrapper cardView, final AbsListView listView) {
			int origHeight = expandingLayout.getHeight();

			ValueAnimator animator = createHeightAnimator(expandingLayout, origHeight, 0);
			animator.addListener(new AnimatorListenerAdapter() {

				@Override
				public void onAnimationEnd(final Animator animator) {
					expandingLayout.setVisibility(View.GONE);

					cardView.setExpanded(false);// card.setExpanded(true);

					notifyAdapter(listView);

					Card card = cardView.getCard();
					if (card.getOnCollapseAnimatorEndListener() != null)
						card.getOnCollapseAnimatorEndListener().onCollapseEnd(card);
				}
			});
			animator.start();
		}

		/**
		 * This method collapse the view that was clicked.
		 *
		 * @param expandingLayout
		 *            layout to collapse
		 * @param cardView
		 *            cardView
		 * @param listView
		 *            listView
		 */
		public static void animateExpanding(final View expandingLayout,
				final CardViewWrapper cardView, final AbsListView listView) {
			/* Update the layout so the extra content becomes visible. */
			expandingLayout.setVisibility(View.VISIBLE);

			View parent = (View) expandingLayout.getParent();
			final int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth()
					- parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.AT_MOST);
			final int heightSpec = View.MeasureSpec
					.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
			expandingLayout.measure(widthSpec, heightSpec);

			ValueAnimator animator = createHeightAnimator(expandingLayout, 0,
					expandingLayout.getMeasuredHeight());
			animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
				final int listViewHeight = listView.getHeight();
				final int listViewBottomPadding = listView.getPaddingBottom();
				final View v = findDirectChild(expandingLayout, listView);

				@Override
				public void onAnimationUpdate(final ValueAnimator valueAnimator) {
					final int bottom = v.getBottom();
					if (bottom > listViewHeight) {
						final int top = v.getTop();
						if (top > 0) {
							listView.smoothScrollBy(
									Math.min(bottom - listViewHeight + listViewBottomPadding, top),
									0);
						}
					}
				}
			});
			animator.addListener(new AnimatorListenerAdapter() {
				@Override
				public void onAnimationEnd(Animator animation) {
					super.onAnimationEnd(animation);
					cardView.setExpanded(true);// card.setExpanded(true);

					notifyAdapter(listView);

					Card card = cardView.getCard();
					if (card.getOnExpandAnimatorEndListener() != null)
						card.getOnExpandAnimatorEndListener().onExpandEnd(card);

				}
			});
			animator.start();
		}

		private static View findDirectChild(final View view, final AbsListView listView) {
			View result = view;
			View parent = (View) result.getParent();
			while (parent != listView) {
				result = parent;
				parent = (View) result.getParent();
			}
			return result;
		}

		public static ValueAnimator createHeightAnimator(final View view, final int start,
				final int end) {
			ValueAnimator animator = ValueAnimator.ofInt(start, end);
			animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

				@Override
				public void onAnimationUpdate(final ValueAnimator valueAnimator) {
					int value = (Integer) valueAnimator.getAnimatedValue();

					ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
					layoutParams.height = value;
					view.setLayoutParams(layoutParams);
				}
			});
			return animator;
		}

		/**
		 * This method notifies the adapter after setting expand value inside
		 * cards
		 *
		 * @param listView
		 */
		public static void notifyAdapter(AbsListView listView) {

			if (listView instanceof CardListView) {

				CardListView cardListView = (CardListView) listView;
				if (cardListView.mAdapter != null) {
					cardListView.mAdapter.notifyDataSetChanged();
				} else if (cardListView.mCursorAdapter != null) {
					// cardListView.mCursorAdapter.notifyDataSetChanged();
				}
			}
		}
	}
}
